﻿namespace HuwmanCode.ComponentModel.Composition.Hosting

open System
open System.ComponentModel.Composition
open System.ComponentModel.Composition.Hosting
open System.ComponentModel.Composition.Primitives
open System.Reflection
open System.ServiceModel
open System.ServiceModel.Channels
open System.ServiceModel.Dispatcher
open System.ServiceModel.Description

open HuwmanCode
open HuwmanCode.Reflection
open HuwmanCode.ComponentModel.Composition

type ServiceExportProvider (assemblies:Assembly[]) =
    inherit ExportProvider ()

    let getServiceType (definition:ImportDefinition) = 
        if definition.ContractName |> String.IsNullOrWhiteSpace then None
        elif definition.Cardinality <> ImportCardinality.ZeroOrOne && definition.Cardinality <> ImportCardinality.ExactlyOne then None
        else assemblies
             |> Array.map (fun asm -> asm.GetTypes ()) |> Array.concat
             |> Array.map (fun typ -> typ,typ.GetCustomAttribute<ExportServiceAttribute> (false))
             |> Array.choose (fun (typ,attr) -> attr |> Option.map (fun attr -> typ,attr.Contract))
             |> Array.filter (fun (sType,cType) -> cType.FullName = definition.ContractName && sType.IsImplementationOf (cType))
             |> Array.filter (fun (_,cType) -> cType.GetCustomAttribute<ServiceContractAttribute> (false) |> Option.isSome)
             |> Array.nthOrNone 0

    let createServiceChannel (serviceType:Type) (contractType:Type) =
        let address = new Uri (sprintf "net.pipe://localhost/%O" (Guid.NewGuid()))
        let binding = new NetNamedPipeBinding(TransactionFlow=true)
        let host = new ServiceHost(serviceType, address)
        host.AddServiceEndpoint (contractType.FullName, binding, "") |> ignore
        host.Open ()

        let channelFactoryType' = typeof<ChannelFactory<_>>
        let channelFactoryType = channelFactoryType'.GetGenericTypeDefinition().MakeGenericType([|contractType|])
        let channelFactory = Activator.CreateInstance (channelFactoryType, binding, address.ToString()) :?> IDisposable
        
        let flags = BindingFlags.Instance ||| BindingFlags.InvokeMethod ||| BindingFlags.Public
        let channel = channelFactoryType.InvokeMember("CreateChannel", flags, null, channelFactory, null)
     
        channel

    override this.GetExportsCore (definition, atomicComposition) =
        let contractName = definition.ContractName
        
        match getServiceType definition with 
        | None -> Seq.empty
        | Some (serviceType,contractType) -> 
            let export = new Export (definition.ContractName, fun () -> createServiceChannel serviceType contractType)
            seq { yield export }